///
// * jdbm LICENSE v1.00
// *
// * Redistribution and use of this software and associated documentation
// * ("Software"), with or without modification, are permitted provided
// * that the following conditions are met:
// *
// * 1. Redistributions of source code must retain copyright
// *    statements and notices.  Redistributions must also contain a
// *    copy of this document.
// *
// * 2. Redistributions in binary form must reproduce the
// *    above copyright notice, this list of conditions and the
// *    following disclaimer in the documentation and/or other
// *    materials provided with the distribution.
// *
// * 3. The name "jdbm" must not be used to endorse or promote
// *    products derived from this Software without prior written
// *    permission of Cees de Groot.  For written permission,
// *    please contact cg@cdegroot.com.
// *
// * 4. Products derived from this Software may not be called "jdbm"
// *    nor may "jdbm" appear in their names without prior written
// *    permission of Cees de Groot.
// *
// * 5. Due credit should be given to the jdbm Project
// *    (http://jdbm.sourceforge.net/).
// *
// * THIS SOFTWARE IS PROVIDED BY THE ndbm PROJECT AND CONTRIBUTORS
// * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
// * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
// * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// * OF THE POSSIBILITY OF SUCH DAMAGE.
// *
// * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
// * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
// * Contributions are Copyright (C) 2000 by their associated contributors.
// *
// * $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
// 

//*************************************************************************
//**  Included in JDMB 1.0 port to SharpDBM;  11/2013 Cyrus Neah cneah@codingreal.com
//**  SharpDBM is an independent reimplementation of the JDBM 1.0 software library in C#  
//*************************************************************************

using System;
using System.Runtime.CompilerServices;
using System.Collections.Generic;

namespace SharpDBM.recman
{



	using RecordManager = SharpDBM.RecordManager;
	using Serializer = SharpDBM.helper.Serializer;
	using DefaultSerializer = SharpDBM.helper.DefaultSerializer;
    using System.IO;

///
// *  This class manages records, which are uninterpreted blobs of data. The
// *  set of operations is simple and straightforward: you communicate with
// *  the class using long "rowids" and byte[] data blocks. Rowids are returned
// *  on inserts and you can stash them away someplace safe to be able to get
// *  back to them. Data blocks can be as long as you wish, and may have
// *  lengths different from the original when updating.
// *  <p>
// *  Operations are synchronized, so that only one of them will happen
// *  concurrently even if you hammer away from multiple threads. Operations
// *  are made atomic by keeping a transaction log which is recovered after
// *  a crash, so the operations specified by this interface all have ACID
// *  properties.
// *  <p>
// *  You identify a file by just the name. The package attaches <tt>.db</tt>
// *  for the database file, and <tt>.lg</tt> for the transaction log. The
// *  transaction log is synchronized regularly and then restarted, so don't
// *  worry if you see the size going up and down.
// *
// * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
// * @author <a href="cg@cdegroot.com">Cees de Groot</a>
// * @version $Id: BaseRecordManager.java,v 1.8 2005/06/25 23:12:32 doomdark Exp $
// 
	public   class BaseRecordManager : RecordManager
	{

//    *
//     * Underlying record file.
//     
		private RecordFile _file;


//    *
//     * Physical row identifier manager.
//     
		private PhysicalRowIdManager _physMgr;


//    *
//     * Logigal to Physical row identifier manager.
//     
		private LogicalRowIdManager _logMgr;


//    *
//     * Page manager.
//     
		private PageManager _pageman;


//    *
//     * Reserved slot for name directory.
//     
		public const int NAME_DIRECTORY_ROOT = 0;


//    *
//     * Static debugging flag
//     
		public const bool DEBUG = false;


//    *
//     * Directory of named ndbmHashtables.  This directory is a persistent
//     * directory, stored as a Hashtable.  It can be retrived by using
//     * the NAME_DIRECTORY_ROOT.
//     
		private Dictionary<string, long> _nameDirectory;


//    *
//     *  Creates a record manager for the indicated file
//     *
//     *  @throws IOException when the file cannot be opened or is not
//     *          a valid file content-wise.
//     
 
//
		public BaseRecordManager(string filename)
		{
			_file = new RecordFile(filename);
			_pageman = new PageManager(_file);
			_physMgr = new PhysicalRowIdManager(_file, _pageman);
			_logMgr = new LogicalRowIdManager(_file, _pageman);
		}


//    *
//     *  Get the underlying Transaction Manager
//     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual TransactionManager getTransactionManager()
		{
			checkIfClosed();

			return _file.txnMgr;
		}


//    *
//     *  Switches off transactioning for the record manager. This means
//     *  that a) a transaction log is not kept, and b) writes aren't
//     *  synch'ed after every update. This is useful when batch inserting
//     *  into a new database.
//     *  <p>
//     *  Only call this method directly after opening the file, otherwise
//     *  the results will be undefined.
//     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void disableTransactions()
		{
			checkIfClosed();

			_file.disableTransactions();
		}


//    *
//     *  Closes the record manager.
//     *
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void close()
		{
			checkIfClosed();

			_pageman.close();
			_pageman = null;

            if (_file != null)
            {
                _file.close();
                _file = null;
            }
		}


//    *
//     *  Inserts a new record using standard java object serialization.
//     *
//     *  @param obj the object for the new record.
//     *  @return the rowid for the new record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     

		public virtual long insert(object obj)
		{
			return insert(obj, DefaultSerializer.INSTANCE);
		}


//    *
//     *  Inserts a new record using a custom serializer.
//     *
//     *  @param obj the object for the new record.
//     *  @param serializer a custom serializer
//     *  @return the rowid for the new record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual long insert(object obj, Serializer serializer)
		{
			sbyte[] data;
			long recid;
			Location physRowId;

			checkIfClosed();

			data = serializer.serialize(obj);
			physRowId = _physMgr.insert(data, 0, data.Length);
			recid = _logMgr.insert(physRowId).toLong();
			if (DEBUG)
			{
				Console.WriteLine("BaseRecordManager.insert() recid " + recid + " length " + data.Length) ;
			}
			return recid;
		}

//    *
//     *  Deletes a record.
//     *
//     *  @param recid the rowid for the record that should be deleted.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void delete(long recid)
		{
			checkIfClosed();
			if (recid <= 0)
			{
				throw new ArgumentOutOfRangeException("Argument 'recid' is invalid: " + recid);
			}

			if (DEBUG)
			{
				Console.WriteLine("BaseRecordManager.delete() recid " + recid) ;
			}

			Location logRowId = new Location(recid);
			Location physRowId = _logMgr.fetch(logRowId);
			_physMgr.delete(physRowId);
			_logMgr.delete(logRowId);
		}


//    *
//     *  Updates a record using standard java object serialization.
//     *
//     *  @param recid the recid for the record that is to be updated.
//     *  @param obj the new object for the record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     

		public virtual void update(long recid, object obj)
		{
			update(recid, obj, DefaultSerializer.INSTANCE);
		}


//    *
//     *  Updates a record using a custom serializer.
//     *
//     *  @param recid the recid for the record that is to be updated.
//     *  @param obj the new object for the record.
//     *  @param serializer a custom serializer
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void update(long recid, object obj, Serializer serializer)
		{
			checkIfClosed();
			if (recid <= 0)
			{
				throw new ArgumentOutOfRangeException("Argument 'recid' is invalid: " + recid);
			}

			Location logRecid = new Location(recid);
			Location physRecid = _logMgr.fetch(logRecid);

			sbyte[] data = serializer.serialize(obj);
			if (DEBUG)
			{
				Console.WriteLine("BaseRecordManager.update() recid " + recid + " length " + data.Length) ;
			}

			Location newRecid = _physMgr.update(physRecid, data, 0, data.Length);
			if (! newRecid.Equals(physRecid))
			{
				_logMgr.update(logRecid, newRecid);
			}
		}


//    *
//     *  Fetches a record using standard java object serialization.
//     *
//     *  @param recid the recid for the record that must be fetched.
//     *  @return the object contained in the record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     

		public virtual object fetch(long recid)
		{
			return fetch(recid, DefaultSerializer.INSTANCE);
		}


//    *
//     *  Fetches a record using a custom serializer.
//     *
//     *  @param recid the recid for the record that must be fetched.
//     *  @param serializer a custom serializer
//     *  @return the object contained in the record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual object fetch(long recid, Serializer serializer)
		{
			sbyte[] data;

			checkIfClosed();
			if (recid <= 0)
			{
				throw new ArgumentOutOfRangeException("Argument 'recid' is invalid: " + recid);
			}
			data = _physMgr.fetch(_logMgr.fetch(new Location(recid)));
			if (DEBUG)
			{
				Console.WriteLine("BaseRecordManager.fetch() recid " + recid + " length " + data.Length) ;
			}
			return serializer.deserialize(data);
		}


//    *
//     *  Returns the number of slots available for "root" rowids. These slots
//     *  can be used to store special rowids, like rowids that point to
//     *  other rowids. Root rowids are useful for bootstrapping access to
//     *  a set of data.
//     
		public virtual int getRootCount()
		{
			return FileHeader.NROOTS;
		}

//    *
//     *  Returns the indicated root rowid.
//     *
//     *  @see #getRootCount
//     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual long getRoot(int id)
		{
			checkIfClosed();

			return _pageman.getFileHeader().getRoot(id);
		}


//    *
//     *  Sets the indicated root rowid.
//     *
//     *  @see #getRootCount
//     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void setRoot(int id, long rowid)
		{
			checkIfClosed();

			_pageman.getFileHeader().setRoot(id, rowid);
		}


//    *
//     * Obtain the record id of a named object. Returns 0 if named object
//     * doesn't exist.
//     

		public virtual long getNamedObject(string name)
		{
			checkIfClosed();

			Dictionary<string, long> nameDirectory = getNameDirectory();
            long recid;
            if (!nameDirectory.TryGetValue(name, out recid))
            {

                return 0;
            }
			
			return recid;
		}

//    *
//     * Set the record id of a named object.
//     

		public virtual void setNamedObject(string name, long recid)
		{
			checkIfClosed();

			Dictionary<string,long> nameDirectory = getNameDirectory();
			if (recid == 0)
			{
			// remove from hashtable
				nameDirectory.Remove(name);
			}
			else
			{
				nameDirectory.Add(name,Convert.ToInt64(recid));
			}
			saveNameDirectory(nameDirectory);
		}


//    *
//     * Commit (make persistent) all changes since beginning of transaction.
//     

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void commit()
		{
			checkIfClosed();

			_pageman.commit();
		}


//    *
//     * Rollback (cancel) all changes since beginning of transaction.
//     

		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void rollback()
		{
			checkIfClosed();

			_pageman.rollback();
		}


//    *
//     * Load name directory
//     

		private Dictionary<string,long> getNameDirectory()
		{
		// retrieve directory of named hashtable
			long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
			if (nameDirectory_recid == 0)
			{
				_nameDirectory = new Dictionary<string, long>();
				nameDirectory_recid = insert(_nameDirectory);
				setRoot(NAME_DIRECTORY_ROOT, nameDirectory_recid);
			}
			else
			{
				_nameDirectory = (Dictionary<string, long>)fetch(nameDirectory_recid);
			}
			return _nameDirectory;
		}



		private void saveNameDirectory(Dictionary<string,long> directory)
		{
			long recid = getRoot(NAME_DIRECTORY_ROOT);
			if (recid == 0)
			{
				throw new IOException("Name directory must exist");
			}
			update(recid, _nameDirectory);
		}


//    *
//     * Check if RecordManager has been closed.  If so, throw an
//     * InvalidOperationException.
//     

		private void checkIfClosed()
		{
			if (_file == null)
			{
				throw new InvalidOperationException("RecordManager has been closed");
			}
		}
	}

}